﻿using CarteConnection;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Sustainalytics.Entities;
using Sustainalytics.OAuth.Services.Common;
using Sustainalytics.Portfolios.BL;
using Sustainalytics.Portfolios.BL.Exceptions;
using Sustainalytics.Portfolios.BL.Model;
using Sustainalytics.Portfolios.Service.ErrorCodes;
using Sustainalytics.Portfolios.Service.Models;
using Sustainalytics.Utils;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Web.Http;
using System.Web.Http.Cors;
using WebApi.OutputCache.V2;

namespace Sustainalytics.Portfolios.Service.Controllers
{
    // [AutoInvalidateCacheOutput]
    [EnableCors(origins: "*", headers: "*", methods: "*")]
    [RoutePrefix("api/v1/portfolios")]
    public class PortfoliosController : ApiController
    {
        private readonly IPortfolioReadHandler _portfolioReadHandler;

        private readonly ICustomPortfolioUpdateHandler _portfolioUpdateHandler;

        private readonly IPortfolioRenameHandler _portfolioRenameHandler;

        private readonly PortfolioDtoCreator _portfolioDtoCreator;

        private readonly IPortfolioDeleteHandler _portfolioDeleteHandler;

        private readonly PortfolioUserClaimsProcessor _userClaimsProcessor;

        private readonly string _cartePath;
        private readonly string _cartePathDelete;
        private readonly string _carteParameter;

        private readonly HttpClient _httpClient;
        private readonly string _exagoAppUrl;
        private readonly string _exagoApiUrl;
        private readonly string _exagoPortfolioReportingReportNameList;

        public PortfoliosController(
            IPortfolioReadHandler portfolioReadHandler,
            PortfolioDtoCreator portfolioDtoCreator,
            ICustomPortfolioUpdateHandler portfolioUpdateHandler,
            IPortfolioRenameHandler portfolioRenameHandler,
            IPortfolioDeleteHandler portfolioDeleteHandler,
            PortfolioUserClaimsProcessor userClaimsProcessor)
        {
            this._portfolioReadHandler = portfolioReadHandler;
            this._portfolioDtoCreator = portfolioDtoCreator;
            this._portfolioUpdateHandler = portfolioUpdateHandler;
            this._portfolioRenameHandler = portfolioRenameHandler;
            this._portfolioDeleteHandler = portfolioDeleteHandler;
            this._userClaimsProcessor = userClaimsProcessor;
            this._cartePath = ConfigurationManager.AppSettings["CartePath"];
            this._carteParameter = ConfigurationManager.AppSettings["CarteParameter"];
            this._cartePathDelete = ConfigurationManager.AppSettings["CartePathDelete"];

            var httpClientHandler = new HttpClientHandler();
            httpClientHandler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => { return true; };

            _httpClient = new HttpClient(httpClientHandler) { Timeout = new TimeSpan(0, 0, 30, 0) };
            _httpClient.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
            _httpClient.DefaultRequestHeaders.Add("Authorization", "Basic Og==");
            _exagoAppUrl = ConfigurationManager.AppSettings["ExagoApplicationUrl"];
            _exagoApiUrl = ConfigurationManager.AppSettings["ExagoApiUrl"];
            _exagoPortfolioReportingReportNameList = ConfigurationManager.AppSettings["ExagoPortfolioReportingReportNameList"];
        }

        // [CacheOutput(ServerTimeSpan = 100, ClientTimeSpan = 0, NoCache = true, MustRevalidate = true, Private = true)]
        [Route("")]
        public HttpResponseMessage Get(
            [FromUri] ListOptions listOptions,
            Guid accountId,
            Guid userId,
            string namePortfolio,
            PortfolioSharingStatus? sharingStatus,
            PortfolioTypes? portfolioType,
            bool? isMyPortfolio,
            bool isNameEqual = false,
            bool newVersion = false)
        {
            var userClaims = _userClaimsProcessor.GetPortfolioClaims();

            var filter = new PortfolioFilter
            {
                Name = namePortfolio,
                SharingStatus = sharingStatus ?? PortfolioSharingStatus.All,
                AddedByUserId = userClaims.UserId,
                IsNameEqual = isNameEqual,
                AccountId = userClaims.AccountId,
                IsMyPortfolio = isMyPortfolio,
                PortfolioType = portfolioType ?? PortfolioTypes.Custom
            };

            if (newVersion)
            {
                if (listOptions.GetCount)
                    return Request.CreateResponse(HttpStatusCode.OK, _portfolioReadHandler.GetPortfoliosCount(filter));
                else
                {
                    var portfolios = GetPortfolioList(listOptions, userClaims, filter);
                    return Request.CreateResponse(HttpStatusCode.OK, portfolios);
                }
            }
            else
            {
                var count = _portfolioReadHandler.GetPortfoliosCount(filter);

                var portfolios = GetPortfolioList(listOptions, userClaims, filter);

                if (listOptions.GetCount)
                {
                    return Request.CreateResponse(
                        HttpStatusCode.OK,
                        new
                        {
                            Portfolios = portfolios,
                            Count = count
                        });
                }

                return Request.CreateResponse(HttpStatusCode.OK, portfolios);
            }
        }

        private IEnumerable<Models.PortfolioBase> GetPortfolioList(ListOptions listOptions, PortfolioUserClaims userClaims, PortfolioFilter filter)
        {
            return _portfolioReadHandler.GetPortfolios(
                filter,
                listOptions.Skip,
                listOptions.Take,
                listOptions.OrderBy,
                listOptions.OrderAscending)
                .Select(p => _portfolioDtoCreator.Create(p, userClaims.UserName, listOptions.IncludeAllFields));
        }

        // [CacheOutput(ServerTimeSpan = 100, ClientTimeSpan = 0, NoCache = true, MustRevalidate = true, Private = true)]
        [Route("{id}")]
        public HttpResponseMessage Get(Guid id, string accountId)
        {
            var userClaims = _userClaimsProcessor.GetPortfolioClaims();
            var portfolio = this._portfolioReadHandler.GetPortfolioById(id, userClaims);
            var portfolioDto = this._portfolioDtoCreator.Create(portfolio, userClaims.UserName, true);
            return this.Request.CreateResponse(HttpStatusCode.OK, portfolioDto);
        }

        [Route("{id}")]
        public HttpResponseMessage Delete(Guid id)
        {
            var userClaims = _userClaimsProcessor.GetPortfolioClaims();
            this._portfolioDeleteHandler.DeletePortfolio(id, userClaims);

            // invalidate cache for parent resource
            var cache = this.Configuration.CacheOutputConfiguration().GetCacheOutputProvider(this.Request);
            cache.RemoveStartsWith(
                this.Configuration.CacheOutputConfiguration().MakeBaseCachekey("Portfolios", "Get"));

            //sending portfolio id to link: CartePath"
            var carteConnection = new PortfolioUpdate(id, "Delete portfolio", _cartePathDelete, _carteParameter);
            carteConnection.SendPortfolioIdToCarte();

            return this.Request.CreateResponse(HttpStatusCode.OK);
        }

        [Route("")]
        public HttpResponseMessage Patch([FromBody] PortfolioSharingDto portfolioShared)
        {
            if (portfolioShared == null)
            {
                return Request.CreateResponse(HttpStatusCode.BadRequest, new { ErrorCode = PortfoliosErrorCodes.PortfolioSharedNull });
            }

            PortfolioSharingStatus newStatus;
            if (!Enum.TryParse(portfolioShared.SharingStatus.ToString(), out newStatus))
            {
                newStatus = PortfolioSharingStatus.Private;
            }

            var userClaims = _userClaimsProcessor.GetPortfolioClaims();

            this._portfolioUpdateHandler.UpdatePortfolioStatus(portfolioShared.Id, newStatus, userClaims);

            // invalidate cache for parent resource
            var cache = this.Configuration.CacheOutputConfiguration().GetCacheOutputProvider(this.Request);
            cache.RemoveStartsWith(
                this.Configuration.CacheOutputConfiguration().MakeBaseCachekey("Portfolios", "Get"));

            return this.Request.CreateResponse(HttpStatusCode.OK);
        }

        [Route("")]
        public HttpResponseMessage Patch([FromUri] Guid id, string newName)
        {
            try
            {
                if (id == Guid.Empty)
                {
                    return this.Request.CreateResponse(
                        HttpStatusCode.BadRequest,
                        new { ErrorCode = PortfoliosErrorCodes.PortfolioNull });
                }

                if (string.IsNullOrEmpty(newName))
                {
                    return this.Request.CreateResponse(
                       HttpStatusCode.BadRequest,
                       new { ErrorCode = PortfoliosErrorCodes.PortfolioNameNull });
                }

                PortfolioUserClaims userClaims = _userClaimsProcessor.GetPortfolioClaims();
                this._portfolioRenameHandler.UpdatePortfolioName(id, newName, userClaims);

                // invalidate cache for parent resource
                var cache = this.Configuration.CacheOutputConfiguration().GetCacheOutputProvider(this.Request);
                cache.RemoveStartsWith(
                    this.Configuration.CacheOutputConfiguration().MakeBaseCachekey("Portfolios", "Get"));

                //sending portfolio id to link: CartePath"
                var carteConnection = new PortfolioUpdate(id, "Update Portfolio Name", _cartePath, _carteParameter);
                carteConnection.SendPortfolioIdToCarte();

                return this.Request.CreateResponse(HttpStatusCode.OK);
            }
            catch (PortfolioModificationUnauthorizedException)
            {
                return this.Request.CreateResponse(
                    HttpStatusCode.Forbidden,
                    new { ErrorCode = PortfoliosErrorCodes.PortfolioRenameUnauthorized });
            }
        }

        [Route("")]
        public HttpResponseMessage Patch([FromUri] Guid portfolioMatchId, double? newWeightValue, bool isUnmatched)
        {
            try
            {
                if (portfolioMatchId == Guid.Empty)
                {
                    return this.Request.CreateResponse(
                        HttpStatusCode.BadRequest,
                        new { ErrorCode = PortfoliosErrorCodes.PortfolioMatchNull });
                }

                if (newWeightValue != null && double.IsNaN((double)newWeightValue))
                {
                    return this.Request.CreateResponse(
                       HttpStatusCode.BadRequest,
                       new { ErrorCode = PortfoliosErrorCodes.PortfolioMatchNewWeightNull });
                }

                if (isUnmatched)
                    this._portfolioUpdateHandler.UpdateSecurityWeight_Unmatched(portfolioMatchId, newWeightValue);
                else
                    this._portfolioUpdateHandler.UpdateSecurityWeight(portfolioMatchId, newWeightValue);

                // invalidate cache for parent resource
                var cache = this.Configuration.CacheOutputConfiguration().GetCacheOutputProvider(this.Request);
                cache.RemoveStartsWith(
                    this.Configuration.CacheOutputConfiguration().MakeBaseCachekey("Portfolios", "Get"));

                //sending portfolio id to link: CartePath"
                var carteConnection = new PortfolioUpdate(portfolioMatchId, "Update Security Weight", _cartePath, _carteParameter);
                carteConnection.SendPortfolioIdToCarte();

                return this.Request.CreateResponse(HttpStatusCode.OK);
            }
            catch (PortfolioModificationUnauthorizedException)
            {
                return this.Request.CreateResponse(
                    HttpStatusCode.Forbidden,
                    new { ErrorCode = PortfoliosErrorCodes.PortfolioSecurityWeightChangeFailed });
            }
        }

        [Route("")]
        public HttpResponseMessage Patch([FromUri] Guid portfolioId, Guid portfolioDetailId, double? newWeightValue, bool isEmptyValue)
        {
            try
            {
                if (portfolioDetailId == Guid.Empty)
                {
                    return this.Request.CreateResponse(
                        HttpStatusCode.BadRequest,
                        new { ErrorCode = PortfoliosErrorCodes.PortfolioMatchNull });
                }

                if (!isEmptyValue && (newWeightValue == null || double.IsNaN((double)newWeightValue)))
                {
                    return this.Request.CreateResponse(
                       HttpStatusCode.BadRequest,
                       new { ErrorCode = PortfoliosErrorCodes.PortfolioMatchNewWeightNull });
                }

                this._portfolioUpdateHandler.UpdatePortfolioDetailsWeight(portfolioDetailId, newWeightValue);

                // invalidate cache for parent resource
                var cache = this.Configuration.CacheOutputConfiguration().GetCacheOutputProvider(this.Request);
                cache.RemoveStartsWith(
                    this.Configuration.CacheOutputConfiguration().MakeBaseCachekey("Portfolios", "Get"));

                //sending portfolio id to link: CartePath"
                var carteConnection = new PortfolioUpdate(portfolioId, "Update Portfolio Details Weight", _cartePath, _carteParameter);
                carteConnection.SendPortfolioIdToCarte();

                return this.Request.CreateResponse(HttpStatusCode.OK);
            }
            catch (PortfolioModificationUnauthorizedException)
            {
                return this.Request.CreateResponse(
                    HttpStatusCode.Forbidden,
                    new { ErrorCode = PortfoliosErrorCodes.PortfolioSecurityWeightChangeFailed });
            }
        }

        public Guid GetJavaGuid(Guid guid)
        {
            string initial = guid.ToString("N");
            List<string> bytes = new List<string>();
            int i = 0;
            while (i<32)
            {
                bytes.Add(initial.Substring(i, 2));
                i += 2;
            }

            // reorder
            string result = String.Format("{0}{1}{2}{3}-{4}{5}-{6}{7}-{8}{9}-{10}{11}{12}{13}{14}{15}",
                bytes[6], bytes[7], bytes[4], bytes[5],
                bytes[0], bytes[1], bytes[2], bytes[3],
                bytes[15], bytes[14], bytes[13], bytes[12],
                bytes[11], bytes[10], bytes[9], bytes[8]);
            return new Guid(result);
        }

        public Guid GetDotNetGuid(Guid guid)
        {
            string initial = guid.ToString("N");
            List<string> bytes = new List<string>();
            int i = 0;
            while (i < 32)
            {
                bytes.Add(initial.Substring(i, 2));
                i += 2;
            }

            // reorder
            string result = String.Format("{0}{1}{2}{3}-{4}{5}-{6}{7}-{8}{9}-{10}{11}{12}{13}{14}{15}",
                bytes[4], bytes[5], bytes[6], bytes[7],
                bytes[2], bytes[3], bytes[0], bytes[1],
                bytes[15], bytes[14], bytes[13], bytes[12],
                bytes[11], bytes[10], bytes[9], bytes[8]);
            return new Guid(result);
        }

        [Route("")]
        public HttpResponseMessage Get([FromUri] Guid portf1, Guid portf2, Guid accountId, string reportName)
        {
            string iframeString = string.Empty;
            HttpResponseMessage response = null;

            var accountIdJavaEncodedId = GetJavaGuid(accountId);
            var portfolio1JavaEncodedId = GetJavaGuid(portf1);
            var portfolio2JavaEncodedId = GetJavaGuid(portf2);

            HttpMethod method = new HttpMethod("POST");
            HttpRequestMessage requestSession = new HttpRequestMessage(method, _exagoApiUrl + "rest/sessions");

            response = _httpClient.SendAsync(requestSession).Result;
            if (!response.IsSuccessStatusCode)
                return Request.CreateErrorResponse(HttpStatusCode.BadRequest, "There was an error processing your request");

            JObject obj = (JObject)JsonConvert.DeserializeObject(response.Content.ReadAsStringAsync().Result);
            string sessionId = obj["Id"].ToString();

            response = SendParameters(portfolio1JavaEncodedId, "PATCH", "rest/parameters/Port1?sid=", sessionId);
            if (!response.IsSuccessStatusCode)
                return Request.CreateErrorResponse(HttpStatusCode.BadRequest, "There was an error processing your request");

            response = SendParameters(portfolio2JavaEncodedId, "PATCH", "rest/parameters/Port2?sid=", sessionId);
            if (!response.IsSuccessStatusCode)
                return Request.CreateErrorResponse(HttpStatusCode.BadRequest, "There was an error processing your request");

            response = SendParameters(accountIdJavaEncodedId, "PATCH", "rest/parameters/accountId?sid=", sessionId);
            if (!response.IsSuccessStatusCode)
                return Request.CreateErrorResponse(HttpStatusCode.BadRequest, "There was an error processing your request");

            response = GetReportUrl("rest/sessions/", "PATCH", sessionId, reportName);
            if (!response.IsSuccessStatusCode)
                return Request.CreateErrorResponse(HttpStatusCode.BadRequest, "There was an error processing your request");

            obj = (JObject)JsonConvert.DeserializeObject(response.Content.ReadAsStringAsync().Result);
            iframeString = _exagoAppUrl + obj["AppUrl"].ToString();

            return Request.CreateResponse(HttpStatusCode.OK, iframeString);
        }

        [Route("")]
        public HttpResponseMessage Get()
        {
            var exagoPortfolioNameList = _exagoPortfolioReportingReportNameList.Split('|'); 
            return Request.CreateResponse(HttpStatusCode.OK, exagoPortfolioNameList);
        }

        private HttpResponseMessage GetReportUrl(string url, string verb, string sessionId, string reportName)
        {
            HttpRequestMessage requestReport = new HttpRequestMessage(new HttpMethod(verb), $"{_exagoApiUrl}{url}{sessionId}");
            var body = new
            {
                Page = "ExagoHome",
                ApiAction = "ExecuteReport",
                ExportType = "pdf",
                ShowTabs = false,
                ShowErrorDetail = true,
                ReportSettings = new { ReportPath = reportName }
            };
            requestReport.Content = new StringContent(JsonConvert.SerializeObject(body), Encoding.UTF8, "application/json");
            requestReport.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
            return _httpClient.SendAsync(requestReport).Result;
        }

        private HttpResponseMessage SendParameters(Guid portfolioId, string verb, string url, string sessionId)
        {
            var changeParameter = new HttpRequestMessage(new HttpMethod(verb), $"{_exagoApiUrl}{url}{sessionId}")
            {
                Content = new StringContent(JsonConvert.SerializeObject(new { Value = portfolioId }), Encoding.UTF8, "application/json")
            };

            return _httpClient.SendAsync(changeParameter).Result;
        }
    }
}